#!/usr/bin/env node

const fs = require("fs-extra");
const path = require("path");
const shell = require("shelljs");
const executablePath = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe';
// const executablePath = 'C:/Users/Administrator/AppData/Local/Google/Chrome/Application/chrome.exe';

async function setTintImage(color, srcFile, destFile) {
  console.log('setTintImage:', { color, srcFile, destFile });
  if (!fs.existsSync(srcFile)) {
    return console.log(`文件: ${srcFile} 不存在`);
  }
  const srcPath = path.dirname(srcFile);
  const extname = path.extname(srcFile);
  const basename = path.basename(srcFile);
  if ([".png", ".jpg", ".jpeg"].indexOf(extname) === -1) {
    return console.log('源文件必须是png，jpg文件');
  }
  if (!destFile) {
    destFile = path.join(srcPath, `${basename}`);
  }
  const base64Type = extname === '.png' ? 'png' : 'jpeg';
  const header = 'data:image/' + base64Type + ';base64,'
  const imageBuf = fs.readFileSync(srcFile);
  const image = header + imageBuf.toString("base64");
  const html = `
    <!DOCTYPE html>
    <html>
    <head>
        <title>tintcolor</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    </head>
    <body>
        <script>
        // Blend modes.
        var kBlendModeDestinationIn = "destination-in";
        var kBlendModeSourceOver = "source-over";
        // Constructor.
        // Takes a single image that should be ready to draw.
        // Note: Use the TintColor.fromURL factory function if you want to load images from the Interwebs.
        var TintColor = function(sourceImage){
            this.sourceImage = sourceImage;
            if (!this.isImageLoaded()) {
                console.warn("TintColor: The source image is not loaded yet, so it may not be displayed.");
            }
        }
        // Factory method will create a TintColor instance from an async request.
        TintColor.fromURL = function(url, handler){
            function TintoImageRequest(src, completionHandler){
                var image = new Image();
                // image.crossOrigin = "Anonymous";
                image.onload = function(){
                    completionHandler(image);
                };
                image.onerror = function(){
                    completionHandler(undefined);
                };
                image.src = src;
                this.image = image;
            }
            TintoImageRequest.prototype.abort = function(){
                this.image.onload = null;
                this.image.onerror = null;
            };
            var operation = new TintoImageRequest(url, function(img){
                var tinto = undefined;
                if (img) {
                    tinto = new TintColor(img);
                }
                if (handler) {
                    handler(tinto);
                }
            });
            return operation;
        };
        // Returns true if the source image is loaded.
        TintColor.prototype.isImageLoaded = function(){
            var source = this.sourceImage;
            if (source) {
                return source.complete && typeof source.naturalWidth != 'undefined' && source.naturalWidth != 0;
            }
            return false;
        };
        // Creates image data with the specified options.
        TintColor.prototype.imageDataWithOptions = function(options){
            if (!this.isImageLoaded()){
                console.warn("TintColor: Cannot create result image. Source image is not available.");
                return null;
            }
            var sourceImage = this.sourceImage;
            var color = options.tintColor;
            var blendingMode = options.blendingMode ? options.blendingMode : kBlendModeDestinationIn;
            var buffer = this.getBuffer();
            var context = buffer.getContext("2d");
            var width = buffer.width;
            var height = buffer.height;
            // If we can't create a context, return the source image's src attribute.
            if (!buffer || !context) {
                return sourceImage.src;
            }
            // Clear the context.
            context.clearRect(0, 0, width, height);
            // Set the fill color.
            context.globalCompositeOperation = kBlendModeSourceOver;
            context.fillStyle = color;
            // Fill the context.
            context.fillRect(0, 0, width, height);
            // Draw image the specified blending mode.
            context.globalCompositeOperation = blendingMode;
            context.drawImage(sourceImage, 0, 0);
            // Finish operation if previous blending mode was not kBlendModeDestinationIn.
            if (blendingMode !== kBlendModeDestinationIn) {
                context.globalCompositeOperation = kBlendModeDestinationIn;
                context.drawImage(sourceImage, 0, 0);
            }
            // Return data in png format.
            var imageData = buffer.toDataURL("image/${base64Type}");
            return imageData;
        }
        // Creates image data using the desired tint color.
        TintColor.prototype.imageDataWithTintColor = function(color){
            return this.imageDataWithOptions({ tintColor : color });
        };
        // Creates image object using the tint color.
        TintColor.prototype.imageWithTintColor = function(color){
            var image = new Image();
            image.src = this.imageDataWithOptions({ tintColor : color });
            return image;
        };
        // Used to obtain the canvas to perform drawing.
        TintColor.prototype.getBuffer = function(){
            var src = this.sourceImage;
            if (typeof src === 'undefined') {
                return null;
            }
            var buffer = this._buffer;
            if (!buffer) {
                buffer = document.createElement("canvas");
                if (typeof src.naturalWidth != 'undefined' && typeof src.naturalHeight != 'undefined') {
                    buffer.width = src.naturalWidth;
                    buffer.height = src.naturalHeight;
                } else {
                    buffer.width = src.width;
                    buffer.height = src.height;
                }
                this._buffer = buffer;
            }
            return buffer;
        };
        var base64;
        function getImageResult() {
            return base64;
        }
        TintColor.fromURL('${image}', function(tinto){
            base64 = tinto.imageDataWithTintColor('${color}');
            window.__end_flag = true;
        });
        </script>
    </body>
    </html>
    `;

  const browser = await require('puppeteer').launch({
    headless: true, // 开启界面
    devtools: false, // 自动开启 F12
    executablePath,
  });
  const page = await browser.newPage();
  await page.setOfflineMode(true);
  // page.on("console", msg => { fs.writeFileSync(destFile, Buffer.from(msg.text().replace(header, ''), 'base64'), 'base64'); browser.close(); });
  await page.setContent(html);
  await page.waitForFunction('window.__end_flag === true');
  const content = await page.evaluate(() => {
    return getImageResult();
  });
  await browser.close();
  const buffer = Buffer.from(content.replace(header, ''), 'base64');
  fs.writeFileSync(destFile, buffer, 'base64');
}
async function editImage(srcFile, destFile, callback, params = {}) {
  if (!fs.existsSync(srcFile)) {
    return console.log(`文件: ${srcFile} 不存在`);
  }
  const srcPath = path.dirname(srcFile);
  const extname = path.extname(srcFile);
  const basename = path.basename(srcFile);
  if ([".png", ".jpg", ".jpeg"].indexOf(extname) === -1) {
    return console.log('源文件必须是png，jpg文件');
  }
  if (!destFile) {
    destFile = path.join(srcPath, `${basename}.png`);
  }
  const base64Type = extname === '.png' ? 'png' : 'jpeg';
  const header = 'data:image/' + base64Type + ';base64,'
  const imageBuf = fs.readFileSync(srcFile);
  const image = header + imageBuf.toString("base64");
  const html = `
    <!DOCTYPE html>
    <html>
    <head>
        <title>editcolor</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <script type="text/javascript">
        ${fs.readFileSync(path.join(__dirname, 'node_modules/lodash/lodash.min.js'))}
        </script>
    </head>
    <body>
    <canvas id="canvas"></canvas>
    <script>
        var base64;
        var c = document.getElementById("canvas");
        var ctx = c.getContext("2d");
        var img = new Image()
        img.src = "${image}";
        img.onload = function () {
            c.width = img.width;
            c.height = img.height;
            ctx.drawImage(img, 0, 0);
            var imgData = ctx.getImageData(0, 0, img.width, img.height);
            var list = imgData.data;
            imgData.data = (${callback})(list, { _, width: img.width, height: img.height }, ${JSON.stringify(params)});
            c.getContext("2d").putImageData(imgData, 0, 0);
            base64 = c.toDataURL("image/png");
            window.__end_flag = true;
        }
        function getImageResult() {
            return base64;
        }
        </script>
    </body>
    </html>
    `;

  const browser = await require('puppeteer').launch({
    headless: true, // 开启界面
    devtools: false, // 自动开启 F12
    executablePath,
  });
  const page = await browser.newPage();
  await page.setOfflineMode(true);
  await page.setContent(html);
  await page.waitForFunction('window.__end_flag === true');
  const content = await page.evaluate(() => {
    return getImageResult();
  });
  await browser.close();
  const str = content.replace('data:image/png;base64,', '');
  const buffer = Buffer.from(str, 'base64');
  fs.writeFileSync(destFile, buffer, 'base64');
}
const matting = (list, { }, { color = `#000000` }) => {
  var r = color.replace('#', '').split('');
  color = { r: +`0x${r[0]}${r[1]}`, g: +`0x${r[2]}${r[3]}`, b: +`0x${r[4]}${r[5]}` };
  var count = list.length / 4;
  for (var i = 0; i < count; i++) {
    var r = list[i * 4 + 0];
    var g = list[i * 4 + 1];
    var b = list[i * 4 + 2];
    var a = Math.sqrt(Math.pow(r - color.r, 2) + Math.pow(g - color.g, 2) + Math.pow(b - color.b, 2));
    list[i * 4 + 3] = a > 5 ? a : 0;
  }
};
const alpha = (list, { _, width, height }, { up = 0 }) => {
  var rate = 1 / 2;
  var n = Math.floor(height * rate);
  var rates = _.map(Array(n), (o, k) => Math.pow((k + 1) / n, 2));
  for (var h = 0; h < n; h++) {
    for (var w = width - 1; w >= 0; w--) {
      var i = (+up ? h : height - 1 - h) * width + w;
      var a = list[i * 4 + 3];
      list[i * 4 + 3] = a * rates[h];
    }
  }
};
const opacity = (list, { }, { isInvert }) => {
  var count = list.length / 4;
  for (var i = 0; i < count; i++) {
    var r = list[i * 4 + 0];
    var g = list[i * 4 + 1];
    var b = list[i * 4 + 2];
    var gray = r * 0.299 + g * 0.587 + b * 0.114;
    var a = isInvert ? gray : 255 - gray;
    list[i * 4 + 0] = 255; // 颜色变为白色
    list[i * 4 + 1] = 255;
    list[i * 4 + 2] = 255;
    list[i * 4 + 3] = a; // 0.1是降低的坡度
  }
};
const replace = (list, { }, { oldColor, newColor, tolerance }) => {
  var r = oldColor.replace('#', '').split('');
  oldColor = { r: +`0x${r[0]}${r[1]}`, g: +`0x${r[2]}${r[3]}`, b: +`0x${r[4]}${r[5]}` };
  r = newColor.replace('#', '').split('');
  newColor = { r: +`0x${r[0]}${r[1]}`, g: +`0x${r[2]}${r[3]}`, b: +`0x${r[4]}${r[5]}` };
  var count = list.length / 4;
  for (var i = 0; i < count; i++) {
    var r = list[i * 4 + 0];
    var g = list[i * 4 + 1];
    var b = list[i * 4 + 2];
    if (Math.abs(r - oldColor.r) < tolerance && Math.abs(g - oldColor.g) < tolerance && Math.abs(b - oldColor.b) < tolerance) {
      list[i * 4 + 0] = newColor.r; // 颜色变为白色
      list[i * 4 + 1] = newColor.g;
      list[i * 4 + 2] = newColor.b;
    }
  }
};
function cleanImage(port = 8080, model = 'lama') {
  shell.exec(`lama-cleaner --model=${model} --device=cpu --port=${port} --host=localhost`);
}
async function scaleImage(src, width, remove) {
  const tmp = '__' + src;
  if (!fs.existsSync(tmp)) {
    fs.copyFileSync(src, tmp);
  }
  const originSize = fs.statSync(tmp).size;
  const image = require('sharp')(tmp);
  const meta = await image.metadata();
  if (meta.width > width) {
    const ret = await image.resize({ width: +width }).toFile(src);
    if (ret.size >= originSize) {
      console.log('压缩后文件大小更大');
      if (remove) fs.moveSync(tmp, src, { overwrite: true })
    } else if (remove) {
      fs.unlinkSync(tmp);
    }
  }
}
async function convertImage(src, dist, width, height) {
  require('sharp')(src).resize(width, height || width).png().toFile(dist, (err, info) => {
    err ? console.error('转换失败:', err) : console.log('转换成功:', info);
  });
}

function main(p1, p2, p3, p4, p5, p6) {
  if (p1 === '-h' || p1 === '--help') {
    console.log('1. 设置图片的tint color，如果没有destFile, 则覆盖原文件');
    console.log('   ximage tint color srcFile [destFile]');
    console.log('   ximage tint color src.jpg(.png) [[xx/]dest.jpg(.png)]');
    console.log('2. 对单一颜色的图片抠图，如果没有destFile, 则覆盖原文件');
    console.log('   ximage matting color srcFile [destFile]');
    console.log('3. 设置图片渐变性透明，如果没有destFile, 则覆盖原文件');
    console.log('   ximage alpha [up=0|1] srcFile [destFile]');
    console.log('   up=0为底部透明，up=1为顶部透明');
    console.log('4. 替换颜色，如果没有destFile, 则覆盖原文件');
    console.log('   ximage replace oldColor newColor tolerance srcFile [destFile]');
    console.log('   tolerance');
    console.log('5. 对图片的替换为深色的部分透明度高，浅色的部分透明度低，这样添加图片背景色就可以显示为不同风格的图片');
    console.log('   ximage opacity [invert=0|1] srcFile [destFile]');
    console.log('6. 缩放图片');
    console.log('   ximage scale 1.png 20 [remove]');
    console.log('7. 转换图片');
    console.log('   ximage convert 1.svg 1.png width [height]');
    console.log('8. 打开网页对图片进行ai去除杂质');
    console.log('   ximage cleaner');
    console.log('   https://github.com/Sanster/lama-cleaner');
    return;
  }
  if (p1 === 'matting') {
    if (/#[0-9a-fA-F]{6}/.test(p2)) {
      editImage(p3, p4, matting, { color: p2 });
    } else {
      editImage(p2, p3, matting);
    }
  } else if (p1 === 'alpha') {
    if (p2 == 1 || p2 == 0) {
      editImage(p3, p4, alpha, { up: p2 });
    } else {
      editImage(p2, p3, alpha);
    }
  } else if (p1 === 'opacity') {
    if (p2 == 1 || p2 == 0) {
      editImage(p3, p4, opacity, { isInvert: p2 });
    } else {
      editImage(p2, p3, opacity);
    }
  } else if (p1 === 'replace') {
    editImage(p5, p6, replace, { oldColor: p2, newColor: p3, tolerance: p4 });
  } else if (p1 === 'tint') {
    setTintImage(p2, p3, p4);
  } else if (p1 === 'cleaner') {
    cleanImage(p2, p3);
  } else if (p1 === 'scale') {
    scaleImage(p2, p3, p4);
  } else if (p1 === 'convert') {
    convertImage(p2, p3, p4, p5);
  }
}

main(...process.argv.slice(2));
